#include "sd_driver.h"

extern "C" {

#include "globals.h"
#include <util/delay.h>
};

volatile uint8_t spi_enabled;

volatile uint32_t startBlock, totalBlocks;
volatile uint8_t SDHC_flag, cardType;

/****************************************************************************
* Purpose:	Sets a flag to disable reading/writing to sd card and prevent
*			lockup on media removal during a read/write call. All data read
*			from sd card thereafter will be garbage
* Input:	No Input
* Output:	No Output
****************************************************************************/
void sdDisableSpi(void)
{
	spi_enabled = 0;
}

/****************************************************************************
* Purpose:	Sets a flag to enable reading/writing to sd card
* Input:	No Input
* Output:	No Output
****************************************************************************/
void sdEnableSpi(void)
{
	spi_enabled = 1;
}

void sdSpiInit(void)
{
	// Set SCK, MOSI, CS as output, MISO as input
	sdPORT.DIR = sdSCK | sdSI | sdCS;

	// Release CS
	SD_CS_DEASSERT;

	// Setup SPI: Enable spi, master mode, mode 0 (clk in data on rising edge), SCK speed = 250KHz (32MHz/128)
	sdSPI.CTRL = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_MODE_0_gc | SD_SPI_LOWSPEED;
}

void sdSpiHighspeed(void)
{
	// Ensure PORT pins are initialized correctly (SCK,MOSI,CS = OUTPUT; MISO = INPUT)
	sdPORT.DIR = sdSCK | sdSI | sdCS;

	// Release CS
	SD_CS_DEASSERT;

	// Clear the spi settings register
	sdSPI.CTRL = 0;

	// Setup SPI: Enable spi, master mode, mode 0 (clk in data on rising edge), SCK speed = 8MHz (32MHz/4)
	sdSPI.CTRL = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_MODE_0_gc | SD_SPI_HIGHSPEED;
}

uint8_t sdSpiSend(uint8_t data)
{
	// Start transmission
	sdSPI.DATA = data;

	// Wait for transmission complete
	while (!(sdSPI.STATUS & SPI_IF_bm) && spi_enabled != 0);

	// Return data recieved

	return (sdSPI.DATA);
}

uint8_t sdSpiRecv(void)
{
	// Load data Register with dummy byte (SPI = FULL Duplex -> slave can only send when the master sends)
	sdSPI.DATA = 0xFF;

	// Wait for transmit to complete
	while (!(sdSPI.STATUS & SPI_IF_bm) && spi_enabled != 0);

	// Return data recieved
	return sdSPI.DATA;
}

uint8_t sdInit(void)
{
	uint8_t i, response, SD_version;
	uint16_t retry = 0;

	for (i = 0; i < 10; i++)
		sdSpiSend(0xff); //80 clock pulses spent before sending the first command

	SD_CS_ASSERT;
	do
	{
		response = sdSendCmd(GO_IDLE_STATE, 0); //send 'reset & go idle' command
		retry++;
		if (retry > 0x20)
		{
			SD_CS_DEASSERT;
			return 1; //time out, card not detected
		}

	}
	while (response != 0x01);

	SD_CS_DEASSERT;

	sdSpiSend(0xff);
	sdSpiSend(0xff);

	retry = 0;

	// Set to otherwise be VER2 Compliant by default
	SD_version = 2;

	//this may change after checking the next command
	do
	{
		//Check power supply status, mendatory for SDHC card
		response = sdSendCmd(SEND_IF_COND, 0x000001AA);

		retry++;
		if (retry > 0xfe)
		{
			SD_version = 1;
			cardType = 1;
			break;
		} //time out

	} while (response != 0x01);

	retry = 0;

	do
	{
		//CMD55, must be sent before sending any ACMD command
		response = sdSendCmd(APP_CMD, 0);

		//ACMD41
		response = sdSendCmd(SD_SEND_OP_COND, 0x40000000);

		retry++;

		if (retry > 0xfe)
		{
			// Timed out
			return 2;
		}

	}
	while (response != 0x00);

	retry = 0;
	SDHC_flag = 0;

	if (SD_version == 2)
	{
		do
		{
			response = sdSendCmd(READ_OCR, 0);
			retry++;
			if (retry > 0xfe)
			{
				cardType = 0;
				break;
			} //time out

		}
		while (response != 0x00);

		if (SDHC_flag == 1)
			cardType = 2;
		else
			cardType = 3;
	}
	return 0; //successful return
}

//******************************************************************
//Function      : to send a command to SD card
//Arguments     : uint8_t (8-bit command value)
//                        & uint32_t (32-bit command argument)
//return        : uint8_t; response byte
//******************************************************************
uint8_t sdSendCmd(uint8_t cmd, uint32_t arg)
{
	uint8_t response, retry = 0, status;

	//SD card accepts byte address while SDHC accepts block address in multiples of 512
	//so, if it's SD card we need to convert block address into corresponding byte address by
	//multipying it with 512. which is equivalent to shifting it left 9 times
	//following 'if' loop does that

	if (SDHC_flag == 0)
		if (cmd == READ_SINGLE_BLOCK || cmd == READ_MULTIPLE_BLOCKS || cmd == WRITE_SINGLE_BLOCK || cmd == WRITE_MULTIPLE_BLOCKS || cmd == ERASE_BLOCK_START_ADDR || cmd == ERASE_BLOCK_END_ADDR)
		{
			arg = arg << 9;
		}

	SD_CS_ASSERT;

	sdSpiSend(cmd | 0x40); //send command, first two bits always '01'
	sdSpiSend(arg >> 24);
	sdSpiSend(arg >> 16);
	sdSpiSend(arg >> 8);
	sdSpiSend(arg);

	if (cmd == SEND_IF_COND) //it is compulsory to send correct CRC for CMD8 (CRC=0x87) & CMD0 (CRC=0x95)
		sdSpiSend(0x87); //for remaining commands, CRC is ignored in SPI mode
	else
		sdSpiSend(0x95);

	while ((response = sdSpiRecv()) == 0xff) //wait response
		if (retry++ > 0xfe)
			break; //time out error

	if (response == 0x00 && cmd == 58) //checking response of CMD58
	{
		status = sdSpiRecv() & 0x40; //first byte of the OCR register (bit 31:24)
		if (status == 0x40)
			SDHC_flag = 1; //we need it to verify SDHC card
		else
			SDHC_flag = 0;

		sdSpiRecv(); //remaining 3 bytes of the OCR register are ignored here
		sdSpiRecv(); //one can use these bytes to check power supply limits of SD
		sdSpiRecv();
	}

	sdSpiRecv(); //extra 8 CLK
	SD_CS_DEASSERT;

	return response; //return state
}

//******************************************************************
//Function      : to read a single block from SD card
//Arguments     : none
//return        : uint8_t; will be 0 if no error,
//                        otherwise the response byte will be sent
//******************************************************************
uint8_t sdReadSingleBlock(uint32_t startBlock, uint8_t * buf)
{
	uint8_t response;
	unsigned int i, retry = 0;

	response = sdSendCmd(READ_SINGLE_BLOCK, startBlock); //read a Block command

	if (response != 0x00)
		return response; //check for SD status: 0x00 - OK (No flags set)

	SD_CS_ASSERT;

	retry = 0;

	while (sdSpiRecv() != 0xfe && spi_enabled!=0) //wait for start block token 0xfe (0x11111110)
	{
		if (retry++ > 0xfffe)
		{
			SD_CS_DEASSERT;
			return 1;
		} //return if time-out
	}

	for (i = 0; i < 512; i++) //read 512 bytes
		buf[i] = sdSpiRecv();

	sdSpiRecv(); //receive incoming CRC (16-bit), CRC is ignored here
	sdSpiRecv();

	sdSpiRecv(); //extra 8 clock pulses
	SD_CS_DEASSERT;

	return 0;
}


//******************************************************************
//Function      : to write to a single block of SD card
//Arguments     : none
//return        : uint8_t; will be 0 if no error,
//                        otherwise the response byte will be sent
//******************************************************************
uint8_t SD_writeSingleBlockBuf(uint32_t startBlock, uint8_t * buf)
{
	uint8_t response;
	unsigned int i, retry = 0;

	response = sdSendCmd(WRITE_SINGLE_BLOCK, startBlock); //write a Block command

	if (response != 0x00)
		return response; //check for SD status: 0x00 - OK (No flags set)

	SD_CS_ASSERT;

	sdSpiSend(0xfe); //Send start block token 0xfe (0x11111110)

	for (i = 0; i < 512; i++) //send 512 bytes data
		sdSpiSend(buf[i]);

	sdSpiSend(0xff); //transmit dummy CRC (16-bit), CRC is ignored here
	sdSpiSend(0xff);

	response = sdSpiRecv();

	if ((response & 0x1f) != 0x05) //response= 0xXXX0AAA1 ; AAA='010' - data accepted
	{ //AAA='101'-data rejected due to CRC error
		SD_CS_DEASSERT; //AAA='110'-data rejected due to write error
		return response;
	}

	while (!sdSpiRecv() && spi_enabled != 0) //wait for SD card to complete writing and get idle
		if (retry++ > 0xfffe)
		{
			SD_CS_DEASSERT;
			return 1;
		}

	SD_CS_DEASSERT;
	sdSpiSend(0xff); //just spend 8 clock cycle delay before reasserting the CS line
	SD_CS_ASSERT; //re-asserting the CS line to verify if card is still busy

	while (!sdSpiRecv() && spi_enabled != 0) //wait for SD card to complete writing and get idle
		if (retry++ > 0xfffe)
		{
			SD_CS_DEASSERT;
			return 1;
		}
	SD_CS_DEASSERT;

	return 0;
}

/****************************************************************************
* Purpose:	Called to initalize sd card and interpret SD_init() return value
* Input:	No Input
* Output:	Returns 0 on success, 1 on failure
****************************************************************************/
int mediaInit()
{
	uint16_t i;
	uint8_t error;

	sdSpiInit();

	cardType = 0;

	//printf("beginning sd initialization\r\n");

	for (i = 0; i < 10; i++)
	{
		error = sdInit();
		if (!error)
			break;
	}

	if (error)
	{
		if (error == 1)
		//	printf("SD card not detected..");
		if (error == 2)
		//	printf("Card Initialization failed..");
		return error;
	}

	//printf("SD Detected...no error\r\n");

	switch (cardType)
	{
		case 1:
		//	printf("SD V1\r\n");
			break;
		case 2:
		//	printf("High Cap Card\r\n");
			break;
		case 3:
		//	printf("SD V2\r\n");
			break;
		default:
		//	printf("Unknown SD Card type\r\n");
			break;
	}

	sdSpiHighspeed();

	sdSpiSend(0xFF);
	sdSpiSend(0xFF);
	sdSpiSend(0xFF);
	sdSpiSend(0xFF);

	return 0;
}

/****************************************************************************
* Purpose:	Wraps the SD_readsingleblockbuf function so we can pass it as
*			a function pointer to fat library
* Input:	Number of sector to be read, pointer to data buffer to write
*			data to
* Output:	Returns 0 on success, otherwise returns response byte
****************************************************************************/
int mediaRead(unsigned long sector, unsigned char *buffer)
{
	sdReadSingleBlock(sector, buffer);
	return 1;
}